import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import "package:pointycastle/export.dart";
import "package:asn1lib/asn1lib.dart";
import 'helpers.dart';
List<int> decodePEM(String pem) {
  var startsWith = [
    "-----BEGIN PUBLIC KEY-----",
    "-----BEGIN PRIVATE KEY-----",
    "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n",
    "-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n",
  ];
  var endsWith = [
    "-----END PUBLIC KEY-----",
    "-----END PRIVATE KEY-----",
    "-----END PGP PUBLIC KEY BLOCK-----",
    "-----END PGP PRIVATE KEY BLOCK-----",
  ];
  bool isOpenPgp = pem.indexOf('BEGIN PGP') != -1;

  for (var s in startsWith) {
    if (pem.startsWith(s)) {
      pem = pem.substring(s.length);
    }
  }

  for (var s in endsWith) {
    if (pem.endsWith(s)) {
      pem = pem.substring(0, pem.length - s.length);
    }
  }

  if (isOpenPgp) {
    var index = pem.indexOf('\r\n');
    pem = pem.substring(0, index);
  }

  pem = pem.replaceAll('\n', '');
  pem = pem.replaceAll('\r', '');

  return base64.decode(paddingBase64(pem));
}
String paddingBase64(String base64Str){
  int i=4-base64Str.length%4;
  if(i==4) return base64Str;
  while(i>0){
    base64Str=base64Str+"=";
    i--;
  }
  return base64Str;
}
class RsaKeyHelper {

  static  RsaKeyHelper _rsaKeyHelper;
  factory RsaKeyHelper()=>_getInstance();
  RsaKeyHelper._internal();
  static RsaKeyHelper get instance=>_getInstance();
  static RsaKeyHelper _getInstance(){
    if(_rsaKeyHelper==null)
      _rsaKeyHelper=RsaKeyHelper._internal();
    return _rsaKeyHelper;
  }
  /**
   * RSA最大加密明文大小
   */
   static final int MAX_ENCRYPT_BLOCK = 117;

  /**
   * RSA最大解密密文大小
   */
   static final int MAX_DECRYPT_BLOCK = 256;

  AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey> generateKeyPair() {
    var keyParams = new RSAKeyGeneratorParameters(BigInt.parse('65537'), 2048, 12);

    var secureRandom = new FortunaRandom();
    var random = new Random.secure();
    List<int> seeds = [];
    for (int i = 0; i < 32; i++) {
      seeds.add(random.nextInt(255));
    }
    secureRandom.seed(new KeyParameter(new Uint8List.fromList(seeds)));

    var rngParams = new ParametersWithRandom(keyParams, secureRandom);
    var k = new RSAKeyGenerator();
    k.init(rngParams);

    return k.generateKeyPair();
  }

  //加密成字节码（utf-8）
   Uint8List encrypt(Uint8List inList, RSAPublicKey publicKey) {
    List<int> outList=[];
    Uint8List cacheList=Uint8List(MAX_ENCRYPT_BLOCK);

    var cipher = new RSAEngine()
      ..init(true, new PublicKeyParameter<RSAPublicKey>(publicKey));

    //   var cipher= new AsymmetricBlockCipher("RSA/PKCS1");
//    cipher.init(true, new PublicKeyParameter<RSAPublicKey>(publicKey));
    print("加密前${inList.length}:${inList}");
    print("cipher.algorithmName:${cipher.algorithmName}");

    int i=0,in_length=inList.length,in_offset=0;
    while(in_length-in_offset>0){
      if(in_length-in_offset>MAX_ENCRYPT_BLOCK){
        cacheList= cipher.process(inList.sublist(in_offset,in_offset+MAX_ENCRYPT_BLOCK));
      }else{
        cacheList= cipher.process(inList.sublist(in_offset));
      }

      outList.addAll(cacheList);

      in_offset=(++i)*MAX_ENCRYPT_BLOCK;
    }

    print("加密后${outList.length}:${outList}");

    return Uint8List.fromList(outList);
  }
  String encrypt2base64(String plainText, RSAPublicKey publicKey) {

    var cipherText = encrypt(utf8.encode(plainText),publicKey);

    return base64.encode(cipherText);
  }
  String encrypt2Hex(String plainText, RSAPublicKey publicKey) {
    var cipherText = encrypt(new Uint8List.fromList(utf8.encode(plainText)),publicKey);
    return formatBytesAsHexString(cipherText);
  }

  Uint8List decrypt(Uint8List inList, RSAPrivateKey privateKey) {
    List<int> outList=[];
    Uint8List cacheList=Uint8List(MAX_DECRYPT_BLOCK);

    print("解密前[${inList.length}]:$inList");

    var cipher = new RSAEngine()
      ..init(false, new PrivateKeyParameter<RSAPrivateKey>(privateKey));
    int i=0,in_length=inList.length,in_offset=0;
    while(in_length-in_offset>0){
      if(in_length-in_offset>MAX_DECRYPT_BLOCK){
        cacheList= cipher.process(inList.sublist(in_offset,in_offset+MAX_DECRYPT_BLOCK));
      }else{
        cacheList= cipher.process(inList.sublist(in_offset));
      }

      outList.addAll(cacheList);

      in_offset=(++i)*MAX_DECRYPT_BLOCK;
    }
    print("解密后[${outList.length}]:$outList");
    return Uint8List.fromList(outList);
  }
  String decryptFromBase64(String cipherBase64, RSAPrivateKey privateKey) {
    List<int> cipherText=base64.decode(cipherBase64);
    var decrypted = decrypt(cipherText,privateKey);

    return utf8.decode(decrypted);
  }
  String decryptFromHex(String cipherHex, RSAPrivateKey privateKey) {
    List<int> cipherText=createUint8ListFromHexString(cipherHex);
    var decrypted = decrypt(new Uint8List.fromList(cipherText),privateKey);
    return utf8.decode(decrypted);
  }

  parsePublicKeyFromPem(pemString) {
    List<int> publicKeyDER = decodePEM(pemString);
    var asn1Parser = new ASN1Parser(publicKeyDER);
    var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
    var publicKeyBitString = topLevelSeq.elements[1];

    var publicKeyAsn = new ASN1Parser(publicKeyBitString.contentBytes());
    ASN1Sequence publicKeySeq = publicKeyAsn.nextObject();
    var modulus = publicKeySeq.elements[0] as ASN1Integer;
    var exponent = publicKeySeq.elements[1] as ASN1Integer;

    RSAPublicKey rsaPublicKey = RSAPublicKey(
        modulus.valueAsBigInteger,
        exponent.valueAsBigInteger
    );

    return rsaPublicKey;
  }

  parsePrivateKeyFromPem(pemString) {
    List<int> privateKeyDER = decodePEM(pemString);
    var asn1Parser = new ASN1Parser(privateKeyDER);
    var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
    var version = topLevelSeq.elements[0];
    var algorithm = topLevelSeq.elements[1];
    var privateKey = topLevelSeq.elements[2];

    asn1Parser = new ASN1Parser(privateKey.contentBytes());
    var pkSeq = asn1Parser.nextObject() as ASN1Sequence;

    version = pkSeq.elements[0];
    var modulus = pkSeq.elements[1] as ASN1Integer;
    var publicExponent = pkSeq.elements[2] as ASN1Integer;
    var privateExponent = pkSeq.elements[3] as ASN1Integer;
    var p = pkSeq.elements[4] as ASN1Integer;
    var q = pkSeq.elements[5] as ASN1Integer;
    var exp1 = pkSeq.elements[6] as ASN1Integer;
    var exp2 = pkSeq.elements[7] as ASN1Integer;
    var co = pkSeq.elements[8] as ASN1Integer;

    RSAPrivateKey rsaPrivateKey = RSAPrivateKey(
        modulus.valueAsBigInteger,
        privateExponent.valueAsBigInteger,
        p.valueAsBigInteger,
        q.valueAsBigInteger
    );

    return rsaPrivateKey;
  }

  encodePublicKeyToPem(RSAPublicKey publicKey) {
    var algorithmSeq = new ASN1Sequence();
    var algorithmAsn1Obj = new ASN1Object.fromBytes(Uint8List.fromList([0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1]));
    var paramsAsn1Obj = new ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
    algorithmSeq.add(algorithmAsn1Obj);
    algorithmSeq.add(paramsAsn1Obj);

    var publicKeySeq = new ASN1Sequence();
    publicKeySeq.add(ASN1Integer(publicKey.modulus));
    publicKeySeq.add(ASN1Integer(publicKey.exponent));
    var publicKeySeqBitString = new ASN1BitString(Uint8List.fromList(publicKeySeq.encodedBytes));

    var topLevelSeq = new ASN1Sequence();
    topLevelSeq.add(algorithmSeq);
    topLevelSeq.add(publicKeySeqBitString);
    var dataBase64 = base64.encode(topLevelSeq.encodedBytes);

    return """-----BEGIN PUBLIC KEY-----\r\n$dataBase64\r\n-----END PUBLIC KEY-----""";
  }

  encodePrivateKeyToPem(RSAPrivateKey privateKey) {
    var version = ASN1Integer(BigInt.from(0));

    var algorithmSeq = new ASN1Sequence();
    var algorithmAsn1Obj = new ASN1Object.fromBytes(Uint8List.fromList([0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1]));
    var paramsAsn1Obj = new ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
    algorithmSeq.add(algorithmAsn1Obj);
    algorithmSeq.add(paramsAsn1Obj);

    var privateKeySeq = new ASN1Sequence();
    var modulus = ASN1Integer(privateKey.n);
    var publicExponent = ASN1Integer(BigInt.parse('65537'));
    var privateExponent = ASN1Integer(privateKey.d);
    var p = ASN1Integer(privateKey.p);
    var q = ASN1Integer(privateKey.q);
    var dP = privateKey.d % (privateKey.p - BigInt.from(1));
    var exp1 = ASN1Integer(dP);
    var dQ = privateKey.d % (privateKey.q - BigInt.from(1));
    var exp2 = ASN1Integer(dQ);
    var iQ = privateKey.q.modInverse(privateKey.p);
    var co = ASN1Integer(iQ);

    privateKeySeq.add(version);
    privateKeySeq.add(modulus);
    privateKeySeq.add(publicExponent);
    privateKeySeq.add(privateExponent);
    privateKeySeq.add(p);
    privateKeySeq.add(q);
    privateKeySeq.add(exp1);
    privateKeySeq.add(exp2);
    privateKeySeq.add(co);
    var publicKeySeqOctetString = new ASN1OctetString(Uint8List.fromList(privateKeySeq.encodedBytes));

    var topLevelSeq = new ASN1Sequence();
    topLevelSeq.add(version);
    topLevelSeq.add(algorithmSeq);
    topLevelSeq.add(publicKeySeqOctetString);
    var dataBase64 = base64.encode(topLevelSeq.encodedBytes);

    return """-----BEGIN PRIVATE KEY-----\r\n$dataBase64\r\n-----END PRIVATE KEY-----""";
  }
}